/*
 * Copyright (C) 2018 Alyssa Rosenzweig <alyssa@rosenzweig.io>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sub license,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include <assert.h>
#include "test-util-3d.h"

/* Include image loading library */
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

static EGLint const config_attribute_list[] = {
	EGL_RED_SIZE, 8,
	EGL_GREEN_SIZE, 8,
	EGL_BLUE_SIZE, 8,
	EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
	EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
	EGL_DEPTH_SIZE, 8,
	EGL_NONE
};

static const EGLint context_attribute_list[] = {
	EGL_CONTEXT_CLIENT_VERSION, 2,
	EGL_NONE
};

static EGLDisplay display;
static EGLConfig config;
static EGLint num_config;
static EGLContext context;
static GLuint program;
const char *vertex_shader_source =
		"attribute vec4 aPosition;    \n"
		"attribute vec4 aColor;       \n"
		"uniform sampler2D depthMap;       \n"

		"uniform float angle;\n"
		"                             \n"
		"varying vec4 vColor;         \n"
		"                             \n"
		"void main()                  \n"
		"{                            \n"
		"    vColor = aColor * vec4(8.02, 1.18, 1, 1);         \n"
		"    float x = aPosition.x;\n"
		"    float y = aPosition.y;\n"
		"    float z = texture2D(depthMap, vColor.xy).r;"
		"    gl_Position = vec4(x, cos(angle)*y - sin(angle)*z, 1.0, 1.0); \n"
		"}                            \n";
const char *fragment_shader_source =
		"precision mediump float;     \n"
		"                             \n"
		"varying vec4 vColor;         \n"
		"uniform sampler2D uTexture;         \n"
		"uniform sampler2D depthMap;       \n"
		"                             \n"
		"void main()                  \n"
		"{                            \n"
//		"    float z = texture2D(depthMap, vColor.xy).r;"
		"    gl_FragColor = texture2D(uTexture, vColor.xy);   \n"
		"}                            \n";

/* Forward mapping implementation:
 *
 * 1. Generate an nxn triangular grid
 * 2. Run mapping in vertex shader
 * 3. Direct texture mapping in fragment shader
 */

struct forward_grid {
	int width, height;

	GLfloat *vVertices;
	GLfloat *vColors;
	unsigned short *indices;
};

static struct forward_grid
generate_grid(int N)
{
	struct forward_grid grid = {
		.width = N,
		.height = N
	};

	/* Allocate room for the vertices/attributes/indices */
	grid.vVertices = calloc(N * N * 3, sizeof(GLfloat));
	grid.vColors = calloc(N * N * 4, sizeof(GLfloat));
	grid.indices = calloc(N * N * 6, sizeof(*grid.indices));

	/* Max number of vertices according to ushort indices */
	assert(N < 128);

	/* We want to subdivide [-1, -1] to [1, 1] in nxn pieces. That is,
	 * iterate in each direction with a step size of 2/(N - 1) to make sure
	 * both 0 and 1 are included */

	float step_size = (2.0) / ((float) (N - 1));

	for (int step_y = 0; step_y < N; ++step_y) {
		float y = step_y * step_size - 0.7f;

		for (int step_x = 0; step_x < N; ++step_x) {
			float x = step_x * step_size - 0.7f;

			/* This grid section is a rect from (x, y) to
			 * (x+step_size, y + step_size) */

			int vertex_number = step_y*N + step_x;

			grid.vVertices[vertex_number*3 + 0] = x;
			grid.vVertices[vertex_number*3 + 1] = y;

			/* Set some attributes */
			grid.vColors[vertex_number*4 + 0] = ((float) step_x) / (N - 1);
			grid.vColors[vertex_number*4 + 1] = ((float) step_y) / (N - 1);
		}
	}

	/* Form indexed buffer from the subdivied vertices. The scheme is to
	 * draw quads connecting adjacent vertices, and split those quads into
	 * triangles. */

	int index_start = 0;

	for (int y = 0; y < (N - 1); ++y) {
		for (int x = 0; x < (N - 1); ++x) {
			/* Connect (x, y) (x + 1, y) (x, y + 1) */
			grid.indices[index_start + 0] = (y + 0)*N + (x + 0);
			grid.indices[index_start + 1] = (y + 0)*N + (x + 1);
			grid.indices[index_start + 2] = (y + 1)*N + (x + 0);

			/* Connect (x + 1, y + 1) (x + 1, y) (x, y + 1).
			 * XXX: Winding order? */

			grid.indices[index_start + 3] = (y + 1)*N + (x + 1);
			grid.indices[index_start + 4] = (y + 0)*N + (x + 1);
			grid.indices[index_start + 5] = (y + 1)*N + (x + 0);

			/* Advance index buffer */
			index_start += 6;
		}
	}

	/* Send the grid off to the GPU */

	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, grid.vVertices);
	glEnableVertexAttribArray(0);

	glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, grid.vColors);
	glEnableVertexAttribArray(1);

	return grid;
}

static void
draw_grid(struct forward_grid *grid)
{
	/* Triangle*2 * the size of the grid */
	glDrawElements(GL_TRIANGLES, 3 * 2 * grid->width * grid->height, GL_UNSIGNED_SHORT, grid->indices);
}

/* Texture related boilerplate */

struct image {
	uint8_t *data;
	int width;
	int height;
	int n;
};

static void
init_texture(struct image *img, int number)
{
	GLuint textures[2], texture_handle;

	glGenTextures(1, &textures);

	glActiveTexture(GL_TEXTURE0 + number);
	glBindTexture(GL_TEXTURE_2D, textures[0]);

	int mode = (img->n == 4) ? GL_RGBA : GL_LUMINANCE;

	glTexImage2D(
	   	GL_TEXTURE_2D, 0, mode,
	   	img->width, img->height, 0,
	   	mode, GL_UNSIGNED_BYTE, img->data);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	texture_handle = glGetUniformLocation(program, number ? "depthMap" : "uTexture");
	glUniform1i(texture_handle, number);
}

/* Depth map generation:
 * The idea is to choose an axis running through the object,
 * and measure perpendicular distance away from that axis.
 */

/* First, consider perpendicular distance "pixel counting". 
 *
 * In particular, we limit to the special case where the axis is pointing
 * completely down. This is ideal to avoid roundoff error which would greatly
 * complicate the routine and later stages. TODO: Rotate the image
 * ahead-of-time.
 *
 * Thus, it is only necessary to know the starting point to count from.
 * Counting looks for pixels in the strictly perpendicular direction; that is,
 * directly to the left or right. At the moment, we assume considerable
 * symmetry and count to the right. TODO: Is this correct? */

static int
count_pixels(struct image *img, int start_x, int start_y)
{
	int distance = 0;

	uint32_t *data32 = (uint32_t *) img->data;

	/* Iterate perpendicularly */

	for (int x = start_x; x < img->width; ++x) {
		uint32_t color = data32[start_y * img->width + x];
		uint8_t alpha = color >> 24;

		/* If there's nothing here, the edge has been found */
		if (alpha == 0) break;

		++distance;
	}

	return distance;
}

#define MIN(a,b) ((a>b)?(b):(a))

/* Next, we generate the 8-bit depth map.
 *
 * We first count pixels along the vertical axis for easy access.
 *
 * We then assign each pixel a depth value based on the depth function and this
 * count */

static struct image
generate_depth_map(struct image *src, int start_x, int start_y)
{
	/* Allocate room for the image and perpendicular lengths */

	struct image dst = {
		.data = calloc(src->width * src->height, 4),
		.width = src->width,
		.height = src->height,
		.n = 4,
	};

	int *lengths = malloc(sizeof(int) * src->height);

	/* Count pixels */

	for (int y = 0; y < src->height; ++y) {
		lengths[y] = count_pixels(src, start_x, y);
	}

	/* Generate depth map */
	uint32_t *data32 = (uint32_t *) dst.data;

	for (int y = 0; y < dst.height; ++y) {
		for (int x = 0; x < dst.width; ++x) {
			float length = lengths[y];
			float coord = x - start_x;
			float depth_det = length*length - coord*coord;

			/* Assume zero depth => nonexistent */
			uint8_t udepth = 0;

			/* If determinant is negative, no depth. If positive,
			 * there is something here; take the square root and
			 * store that. */

			if (depth_det >= 0) {
				float depth = sqrtf(depth_det);
				int d = depth * 4;

				data32[(y * dst.width) + x] = MIN(d, 255);
			}
		}
	}

	return dst;
}

int main()
{
	GLint width, height;
	EGLSurface surface;

	display = get_display();

	/* get an appropriate EGL frame buffer configuration */
	eglChooseConfig(display, config_attribute_list, &config, 1, &num_config);

	/* create an EGL rendering context */
	context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attribute_list);

	surface = make_window(display, config, 2048, 1280);

	eglQuerySurface(display, surface, EGL_WIDTH, &width);
	eglQuerySurface(display, surface, EGL_HEIGHT, &height);

	/* connect the context to the surface */
	eglMakeCurrent(display, surface, surface, context);

	program = get_program(vertex_shader_source, fragment_shader_source);

	glBindAttribLocation(program, 0, "aPosition");
	glBindAttribLocation(program, 1, "aColor");

	link_program(program);

	glViewport(0, 0, width, height);

	/* Load the image */

	struct image img;

	img.data = stbi_load("pencil.png", &img.width, &img.height, &img.n, 0);
	assert(img.data != NULL);
	assert(img.n == 4);

	/* XXX: We probably want a better way to input these */
	int start_x = img.width >> 1;
	int start_y = 0;

	struct image depth_map = generate_depth_map(&img, start_x, start_y);

	init_texture(&img, 0);
	init_texture(&depth_map, 1);

	/* Clean up */
	stbi_image_free(img.data);

	/* Generate the forward mapping grid */
	struct forward_grid grid = generate_grid(127);

	/* Setup the angle */
	int u_angle = glGetUniformLocation(program, "angle");
	float angle = 0.0f;

	for (;;) {
		glClearColor(0.5, 0.0, 0.0, 1.0);
		glClear(GL_COLOR_BUFFER_BIT);

		glUniform1f(u_angle, angle);

		draw_grid(&grid);

		eglSwapBuffers(display, surface);
		glFlush();

		angle += (3.14159f/180.0f);
	}

	eglDestroySurface(display, surface);

	eglTerminate(display);

	return 0;
}
